1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.codehaus.groovy.runtime;
20  
21  import groovy.lang.Closure;
22  import groovy.lang.GeneratedGroovyProxy;
23  import groovy.lang.GroovyClassLoader;
24  import groovy.lang.GroovyObject;
25  import groovy.lang.GroovyRuntimeException;
26  import groovy.transform.Trait;
27  import org.codehaus.groovy.ast.ClassHelper;
28  import org.codehaus.groovy.ast.ClassNode;
29  import org.codehaus.groovy.classgen.asm.BytecodeHelper;
30  import org.codehaus.groovy.control.CompilationUnit;
31  import org.codehaus.groovy.control.CompilerConfiguration;
32  import org.codehaus.groovy.control.ErrorCollector;
33  import org.codehaus.groovy.control.Phases;
34  import org.codehaus.groovy.control.SourceUnit;
35  import org.codehaus.groovy.tools.GroovyClass;
36  import org.codehaus.groovy.transform.trait.Traits;
37  import org.objectweb.asm.ClassVisitor;
38  import org.objectweb.asm.ClassWriter;
39  import org.objectweb.asm.Label;
40  import org.objectweb.asm.MethodVisitor;
41  import org.objectweb.asm.Opcodes;
42  import org.objectweb.asm.Type;
43  
44  import java.lang.annotation.Annotation;
45  import java.lang.reflect.Constructor;
46  import java.lang.reflect.InvocationTargetException;
47  import java.lang.reflect.Method;
48  import java.lang.reflect.Modifier;
49  import java.security.AccessController;
50  import java.security.PrivilegedAction;
51  import java.util.ArrayList;
52  import java.util.Arrays;
53  import java.util.Collection;
54  import java.util.Collections;
55  import java.util.HashMap;
56  import java.util.HashSet;
57  import java.util.LinkedHashSet;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.Set;
61  import java.util.concurrent.atomic.AtomicLong;
62  
63  /**
64   * A proxy generator responsible for mapping a map of closures to a class implementing a list of interfaces. For
65   * example, the following code:
66   * <pre>
67   *     abstract class Foo {
68   *         abstract void bar();
69   *         abstract void baz();
70   *     }
71   *     def dyn = [bar: { println 'hello' }, baz: { println 'world'}] as Foo
72   * </pre>
73   * will generate a proxy class which extends class <i>Foo</i> and delegates method calls to the provided closures.
74   *
75   * The generated proxy implements the {@link GroovyObject} interface.
76   *
77   * Additionaly, this proxy generator supports delegation to another object. In that case, if a method is defined
78   * both in the closure map and the delegate, the version from the map is preferred. This allows overriding methods
79   * from delegates with ease.
80   *
81   * Internally, the proxy generator makes use of ASM to generate bytecode, for improved performance as compared
82   * to the legacy proxy generation mechanism which made use of string templates.
83   *
84   * @author Cedric Champeau
85   *
86   * @since 2.0.0
87   */
88  public class ProxyGeneratorAdapter extends ClassVisitor implements Opcodes {
89      private static final Map<String, Boolean> EMPTY_DELEGATECLOSURE_MAP = Collections.emptyMap();
90      private static final Set<String> EMPTY_STRING_SET = Collections.emptySet();
91  
92      private static final String CLOSURES_MAP_FIELD = "$closures$delegate$map";
93      private static final String DELEGATE_OBJECT_FIELD = "$delegate";
94      private static List<Method> OBJECT_METHODS = getInheritedMethods(Object.class, new ArrayList<Method>());
95      private static List<Method> GROOVYOBJECT_METHODS = getInheritedMethods(GroovyObject.class, new ArrayList<Method>());
96  
97      private static final AtomicLong pxyCounter = new AtomicLong();
98      private static final Set<String> GROOVYOBJECT_METHOD_NAMESS;
99      private static final Object[] EMPTY_ARGS = new Object[0];
100 
101     static {
102         List<String> names = new ArrayList<String>();
103         for (Method method : GroovyObject.class.getMethods()) {
104             names.add(method.getName());
105         }
106         GROOVYOBJECT_METHOD_NAMESS = new HashSet<String>(names);
107     }
108 
109     private final Class superClass;
110     private final Class delegateClass;
111     private final InnerLoader loader;
112     private final String proxyName;
113     private final LinkedHashSet<Class> classList;
114     private final Map<String, Boolean> delegatedClosures;
115 
116     // if emptyBody == true, then we generate an empty body instead throwing error on unimplemented methods
117     private final boolean emptyBody;
118     private final boolean hasWildcard;
119     private final boolean generateDelegateField;
120     private final Set<String> objectDelegateMethods;
121 
122     private final Set<Object> visitedMethods;
123 
124     // cached class
125     private final Class cachedClass;
126     private final Constructor cachedNoArgConstructor;
127 
128     /**
129      * Construct a proxy generator. This generator is used when we need to create a proxy object for a class or an
130      * interface given a map of closures.
131      *
132      * @param closureMap the delegates implementations
133      * @param superClass corresponding to the superclass class visitor
134      * @param interfaces extra interfaces the proxy should implement
135      * @param proxyLoader the class loader which should be used to load the generated proxy
136      * @param delegateClass if not null, generate a delegate field with the corresponding class
137      * @param emptyBody if set to true, the unimplemented abstract methods will receive an empty body instead of
138      *                  throwing an {@link UnsupportedOperationException}.
139      */
140     public ProxyGeneratorAdapter(
141             final Map<Object, Object> closureMap,
142             final Class superClass,
143             final Class[] interfaces,
144             final ClassLoader proxyLoader,
145             final boolean emptyBody,
146             final Class delegateClass) {
147         super(Opcodes.ASM4, new ClassWriter(0));
148         this.loader = proxyLoader!=null?createInnerLoader(proxyLoader):findClassLoader(superClass);
149         this.visitedMethods = new LinkedHashSet<Object>();
150         this.delegatedClosures = closureMap.isEmpty()? EMPTY_DELEGATECLOSURE_MAP :new HashMap<String, Boolean>();
151         boolean wildcard = false;
152         for (Map.Entry<Object, Object> entry : closureMap.entrySet()) {
153             String name = entry.getKey().toString();
154             if ("*".equals(name)) {
155                 wildcard = true;
156             }
157             this.delegatedClosures.put(name, Boolean.FALSE);
158         }
159         this.hasWildcard = wildcard;
160 
161         Class fixedSuperClass = adjustSuperClass(superClass, interfaces);
162         // if we have to delegate to another object, generate the appropriate delegate field
163         // and collect the name of the methods for which delegation is active
164         this.generateDelegateField = delegateClass!=null;
165         this.objectDelegateMethods = generateDelegateField?createDelegateMethodList(fixedSuperClass, delegateClass, interfaces):EMPTY_STRING_SET;
166         this.delegateClass = delegateClass;
167 
168         // a proxy is supposed to be a concrete class, so it cannot extend an interface.
169         // If the provided superclass is an interface, then we replace the superclass with Object
170         // and add this interface to the list of implemented interfaces
171         this.superClass = fixedSuperClass;
172 
173         // create the base list of classes which have possible methods to be overloaded
174         this.classList = new LinkedHashSet<Class>();
175         this.classList.add(superClass);
176         if (generateDelegateField) {
177             classList.add(delegateClass);
178             Collections.addAll(this.classList, delegateClass.getInterfaces());
179         }
180         if (interfaces!=null) {
181             Collections.addAll(this.classList, interfaces);
182         }
183         this.proxyName = proxyName();
184         this.emptyBody = emptyBody;
185 
186         // generate bytecode
187         ClassWriter writer = (ClassWriter) cv;
188         this.visit(Opcodes.V1_5, ACC_PUBLIC, proxyName, null, null, null);
189         byte[] b = writer.toByteArray();
190 //        CheckClassAdapter.verify(new ClassReader(b), true, new PrintWriter(System.err));
191         cachedClass = loader.defineClass(proxyName.replace('/','.'), b);
192         // cache no-arg constructor
193         Class[] args = generateDelegateField?new Class[] { Map.class, delegateClass }:new Class[] { Map.class };
194         Constructor constructor;
195         try {
196             constructor = cachedClass.getConstructor(args);
197         } catch (NoSuchMethodException e) {
198             constructor = null;
199         }
200         cachedNoArgConstructor = constructor;
201     }
202 
203     private Class adjustSuperClass(Class superClass, final Class[] interfaces) {
204         boolean isSuperClassAnInterface = superClass.isInterface();
205         if (!isSuperClassAnInterface) {
206             return superClass;
207         }
208         Class result = Object.class;
209         Set<ClassNode> traits = new LinkedHashSet<ClassNode>();
210         // check if it's a trait
211         collectTraits(superClass, traits);
212         if (interfaces!=null) {
213             for (Class anInterface : interfaces) {
214                 collectTraits(anInterface, traits);
215             }
216         }
217         if (!traits.isEmpty()) {
218             String name = superClass.getName() + "$TraitAdapter";
219             ClassNode cn = new ClassNode(name, ACC_PUBLIC | ACC_ABSTRACT, ClassHelper.OBJECT_TYPE, traits.toArray(new ClassNode[traits.size()]), null);
220             CompilationUnit cu = new CompilationUnit(loader);
221             CompilerConfiguration config = new CompilerConfiguration();
222             SourceUnit su = new SourceUnit(name+"wrapper", "", config, loader, new ErrorCollector(config));
223             cu.addSource(su);
224             cu.compile(Phases.CONVERSION);
225             su.getAST().addClass(cn);
226             cu.compile(Phases.CLASS_GENERATION);
227             @SuppressWarnings("unchecked")
228             List<GroovyClass> classes = (List<GroovyClass>) cu.getClasses();
229             for (GroovyClass groovyClass : classes) {
230                 if (groovyClass.getName().equals(name)) {
231                     return loader.defineClass(name,groovyClass.getBytes());
232                 }
233             }
234         }
235         return result;
236     }
237 
238     private void collectTraits(final Class clazz, final Set<ClassNode> traits) {
239         Annotation annotation = clazz.getAnnotation(Trait.class);
240         if (annotation!=null) {
241             ClassNode trait = ClassHelper.make(clazz);
242             traits.add(trait.getPlainNodeReference());
243             LinkedHashSet<ClassNode> selfTypes = new LinkedHashSet<ClassNode>();
244             Traits.collectSelfTypes(trait, selfTypes, true, true);
245             for (ClassNode selfType : selfTypes) {
246                 if (Traits.isTrait(selfType)) {
247                     traits.add(selfType.getPlainNodeReference());
248                 }
249             }
250         }
251     }
252 
253     private static InnerLoader createInnerLoader(final ClassLoader parent) {
254         return AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
255             public InnerLoader run() {
256                 return new InnerLoader(parent);
257             }
258         });
259     }
260 
261     private InnerLoader findClassLoader(Class clazz) {
262         ClassLoader cl = clazz.getClassLoader();
263         if (cl==null) cl = this.getClass().getClassLoader();
264         return createInnerLoader(cl);
265     }
266 
267     private static Set<String> createDelegateMethodList(Class superClass, Class delegateClass, Class[] interfaces) {
268         Set<String> selectedMethods = new HashSet<String>();
269         List<Method> interfaceMethods = new ArrayList<Method>();
270         List<Method> superClassMethods = new ArrayList<Method>();
271         Collections.addAll(superClassMethods, superClass.getDeclaredMethods());
272         if (interfaces!=null) {
273             for (Class thisInterface : interfaces) {
274                 getInheritedMethods(thisInterface, interfaceMethods);
275             }
276             for (Method method : interfaceMethods) {
277                 if (!(containsEquivalentMethod(superClassMethods, method))) {
278                     selectedMethods.add(method.getName()+Type.getMethodDescriptor(method));
279                 }
280             }
281         }
282         List<Method> additionalMethods = getInheritedMethods(delegateClass, new ArrayList<Method>());
283         for (Method method : additionalMethods) {
284             if (method.getName().indexOf('$') != -1)
285                 continue;
286             if (!containsEquivalentMethod(interfaceMethods, method) &&
287                     !containsEquivalentMethod(OBJECT_METHODS, method) &&
288                     !containsEquivalentMethod(GROOVYOBJECT_METHODS, method)) {
289                 selectedMethods.add(method.getName()+Type.getMethodDescriptor(method));
290             }
291         }
292         return selectedMethods;
293     }
294 
295     private static List<Method> getInheritedMethods(Class baseClass, List<Method> methods) {
296         Collections.addAll(methods, baseClass.getMethods());
297         Class currentClass = baseClass;
298         while (currentClass != null) {
299             Method[] protectedMethods = currentClass.getDeclaredMethods();
300             for (Method method : protectedMethods) {
301                 if (method.getName().indexOf('$') != -1)
302                     continue;
303                 if (Modifier.isProtected(method.getModifiers()) && !containsEquivalentMethod(methods, method))
304                     methods.add(method);
305             }
306             currentClass = currentClass.getSuperclass();
307         }
308         return methods;
309     }
310 
311     private static boolean containsEquivalentMethod(Collection<Method> publicAndProtectedMethods, Method candidate) {
312         for (Method method : publicAndProtectedMethods) {
313             if (candidate.getName().equals(method.getName()) &&
314                     candidate.getReturnType().equals(method.getReturnType()) &&
315                     hasMatchingParameterTypes(candidate, method)) {
316                 return true;
317             }
318         }
319         return false;
320     }
321 
322     private static boolean hasMatchingParameterTypes(Method method, Method candidate) {
323         Class[] candidateParamTypes = candidate.getParameterTypes();
324         Class[] methodParamTypes = method.getParameterTypes();
325         if (candidateParamTypes.length != methodParamTypes.length) return false;
326         for (int i = 0; i < methodParamTypes.length; i++) {
327             if (!candidateParamTypes[i].equals(methodParamTypes[i])) return false;
328         }
329         return true;
330     }
331 
332     @Override
333     public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
334         Set<String> interfacesSet = new LinkedHashSet<String>();
335         if (interfaces != null) Collections.addAll(interfacesSet, interfaces);
336         for (Class extraInterface : classList) {
337             if (extraInterface.isInterface()) interfacesSet.add(BytecodeHelper.getClassInternalName(extraInterface));
338         }
339         final boolean addGroovyObjectSupport = !GroovyObject.class.isAssignableFrom(superClass);
340         if (addGroovyObjectSupport) interfacesSet.add("groovy/lang/GroovyObject");
341         if (generateDelegateField) {
342             classList.add(GeneratedGroovyProxy.class);
343             interfacesSet.add("groovy/lang/GeneratedGroovyProxy");
344         }
345         super.visit(V1_5, ACC_PUBLIC, proxyName, signature, BytecodeHelper.getClassInternalName(superClass), interfacesSet.toArray(new String[interfacesSet.size()]));
346         visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
347         addDelegateFields();
348         if (addGroovyObjectSupport) {
349             createGroovyObjectSupport();
350         }
351         for (Class clazz : classList) {
352             visitClass(clazz);
353         }
354     }
355 
356     /**
357      * Visit every class/interface this proxy should implement, and generate the appropriate
358      * bytecode for delegation if available.
359      * @param clazz an class for which to generate bytecode
360      */
361     private void visitClass(final Class clazz) {
362         Method[] methods = clazz.getDeclaredMethods();
363         for (Method method : methods) {
364             Class<?>[] exceptionTypes = method.getExceptionTypes();
365             String[] exceptions = new String[exceptionTypes.length];
366             for (int i = 0; i < exceptions.length; i++) {
367                 exceptions[i] = BytecodeHelper.getClassInternalName(exceptionTypes[i]);
368             }
369             // for each method defined in the class, generate the appropriate delegation bytecode
370             visitMethod(method.getModifiers(),
371                     method.getName(),
372                     BytecodeHelper.getMethodDescriptor(method.getReturnType(), method.getParameterTypes()),
373                     null,
374                     exceptions);
375         }
376         Constructor[] constructors = clazz.getDeclaredConstructors();
377         for (Constructor method : constructors) {
378             Class<?>[] exceptionTypes = method.getExceptionTypes();
379             String[] exceptions = new String[exceptionTypes.length];
380             for (int i = 0; i < exceptions.length; i++) {
381                 exceptions[i] = BytecodeHelper.getClassInternalName(exceptionTypes[i]);
382             }
383             // for each method defined in the class, generate the appropriate delegation bytecode
384             visitMethod(method.getModifiers(),
385                     "<init>",
386                     BytecodeHelper.getMethodDescriptor(Void.TYPE, method.getParameterTypes()),
387                     null,
388                     exceptions);
389         }
390 
391         for (Class intf : clazz.getInterfaces()) {
392             visitClass(intf);
393         }
394         Class superclass = clazz.getSuperclass();
395         if (superclass!=null) visitClass(superclass);
396 
397         // Ultimately, methods can be available in the closure map which are not defined by the superclass
398         // nor the interfaces
399         for (Map.Entry<String, Boolean> entry : delegatedClosures.entrySet()) {
400             Boolean visited = entry.getValue();
401             if (!visited) {
402                 String name = entry.getKey();
403                 if (!"*".equals(name)) {
404                     // generate a new method
405                     visitMethod(ACC_PUBLIC, name, "([Ljava/lang/Object;)Ljava/lang/Object;", null, null);
406                 }
407             }
408         }
409     }
410 
411     /**
412      * When an object doesn't implement the GroovyObject interface, we generate bytecode for the
413      * {@link GroovyObject} interface methods. Otherwise, the superclass is expected to implement them.
414      */
415     private void createGroovyObjectSupport() {
416         visitField(ACC_PRIVATE + ACC_TRANSIENT, "metaClass", "Lgroovy/lang/MetaClass;", null, null);
417 
418         // getMetaClass
419         MethodVisitor mv;
420         {
421             mv = super.visitMethod(ACC_PUBLIC, "getMetaClass", "()Lgroovy/lang/MetaClass;", null, null);
422             mv.visitCode();
423             Label l0 = new Label();
424             mv.visitLabel(l0);
425             mv.visitVarInsn(ALOAD, 0);
426             mv.visitFieldInsn(GETFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
427             Label l1 = new Label();
428             mv.visitJumpInsn(IFNONNULL, l1);
429             Label l2 = new Label();
430             mv.visitLabel(l2);
431             mv.visitVarInsn(ALOAD, 0);
432             mv.visitVarInsn(ALOAD, 0);
433             mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
434             mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "getMetaClass", "(Ljava/lang/Class;)Lgroovy/lang/MetaClass;", false);
435             mv.visitFieldInsn(PUTFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
436             mv.visitLabel(l1);
437             mv.visitVarInsn(ALOAD, 0);
438             mv.visitFieldInsn(GETFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
439             mv.visitInsn(ARETURN);
440             mv.visitMaxs(2, 1);
441             mv.visitEnd();
442         }
443 
444         // getProperty
445         {
446             mv = super.visitMethod(ACC_PUBLIC, "getProperty", "(Ljava/lang/String;)Ljava/lang/Object;", null, null);
447             mv.visitCode();
448             mv.visitIntInsn(ALOAD, 0);
449             mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/GroovyObject", "getMetaClass", "()Lgroovy/lang/MetaClass;", true);
450             mv.visitIntInsn(ALOAD, 0);
451             mv.visitVarInsn(ALOAD, 1);
452             mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "getProperty", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", true);
453             mv.visitInsn(ARETURN);
454             mv.visitMaxs(3, 2);
455             mv.visitEnd();
456         }
457 
458         // setProperty
459         {
460             mv = super.visitMethod(ACC_PUBLIC, "setProperty", "(Ljava/lang/String;Ljava/lang/Object;)V", null, null);
461             mv.visitCode();
462             Label l0 = new Label();
463             mv.visitLabel(l0);
464             mv.visitVarInsn(ALOAD, 0);
465             mv.visitMethodInsn(INVOKEVIRTUAL, proxyName, "getMetaClass", "()Lgroovy/lang/MetaClass;", false);
466             mv.visitVarInsn(ALOAD, 0);
467             mv.visitVarInsn(ALOAD, 1);
468             mv.visitVarInsn(ALOAD, 2);
469             mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "setProperty", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", true);
470             Label l1 = new Label();
471             mv.visitLabel(l1);
472             mv.visitInsn(RETURN);
473             Label l2 = new Label();
474             mv.visitLabel(l2);
475             mv.visitMaxs(4, 3);
476             mv.visitEnd();
477         }
478 
479         // invokeMethod
480         {
481             mv = super.visitMethod(ACC_PUBLIC, "invokeMethod", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", null, null);
482             Label l0 = new Label();
483             mv.visitLabel(l0);
484             mv.visitVarInsn(ALOAD, 0);
485             mv.visitMethodInsn(INVOKEVIRTUAL, proxyName, "getMetaClass", "()Lgroovy/lang/MetaClass;", false);
486             mv.visitVarInsn(ALOAD, 0);
487             mv.visitVarInsn(ALOAD, 1);
488             mv.visitVarInsn(ALOAD, 2);
489             mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", true);
490             mv.visitInsn(ARETURN);
491             Label l1 = new Label();
492             mv.visitLabel(l1);
493             mv.visitMaxs(4, 3);
494             mv.visitEnd();
495         }
496 
497         // setMetaClass
498         {
499             mv = super.visitMethod(ACC_PUBLIC, "setMetaClass", "(Lgroovy/lang/MetaClass;)V", null, null);
500             mv.visitCode();
501             Label l0 = new Label();
502             mv.visitLabel(l0);
503             mv.visitVarInsn(ALOAD, 0);
504             mv.visitVarInsn(ALOAD, 1);
505             mv.visitFieldInsn(PUTFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
506             Label l1 = new Label();
507             mv.visitLabel(l1);
508             mv.visitInsn(RETURN);
509             Label l2 = new Label();
510             mv.visitLabel(l2);
511             mv.visitMaxs(2, 2);
512             mv.visitEnd();
513         }
514 
515     }
516 
517     /**
518      * Creates delegate fields for every closure defined in the map.
519      */
520     private void addDelegateFields() {
521         visitField(ACC_PRIVATE + ACC_FINAL, CLOSURES_MAP_FIELD, "Ljava/util/Map;", null, null);
522         if (generateDelegateField) {
523             visitField(ACC_PRIVATE+ACC_FINAL, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass), null, null);
524         }
525     }
526 
527     private String proxyName() {
528         String name = delegateClass!=null?delegateClass.getName():superClass.getName();
529         int index = name.lastIndexOf('.');
530         if (index == -1) return name + pxyCounter.incrementAndGet() + "_groovyProxy";
531         return name.substring(index + 1, name.length()) + pxyCounter.incrementAndGet() + "_groovyProxy";
532     }
533 
534     private static boolean isImplemented(Class clazz, String name, String desc) {
535         Method[] methods = clazz.getDeclaredMethods();
536         for (Method method : methods) {
537             if (method.getName().equals(name)) {
538                 if (desc.equals(Type.getMethodDescriptor(method))) {
539                     return !Modifier.isAbstract(method.getModifiers());
540                 }
541             }
542         }
543         Class parent = clazz.getSuperclass();
544         if (parent !=null) {
545             return isImplemented(parent, name, desc);
546         }
547         return false;
548     }
549 
550     @Override
551     public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
552         Object key = Arrays.asList(name, desc);
553         if (visitedMethods.contains(key)) return null;
554         if (Modifier.isPrivate(access) || Modifier.isNative(access) || ((access&ACC_SYNTHETIC)!=0)) {
555             // do not generate bytecode for private methods
556             return null;
557         }
558         int accessFlags = access;
559         visitedMethods.add(key);
560         if ((objectDelegateMethods.contains(name+desc) || delegatedClosures.containsKey(name) || (!"<init>".equals(name) && hasWildcard)) && !Modifier.isStatic(access) && !Modifier.isFinal(access)) {
561             if (!GROOVYOBJECT_METHOD_NAMESS.contains(name)) {
562                 if (Modifier.isAbstract(access)) {
563                     // prevents the proxy from being abstract
564                     accessFlags -= ACC_ABSTRACT;
565                 }
566                 if (delegatedClosures.containsKey(name) || (!"<init>".equals(name) && hasWildcard)) {
567                     delegatedClosures.put(name, Boolean.TRUE);
568                     return makeDelegateToClosureCall(name, desc, signature, exceptions, accessFlags);
569                 }
570                 if (generateDelegateField && objectDelegateMethods.contains(name+desc)) {
571                     return makeDelegateCall(name, desc,  signature, exceptions, accessFlags);
572                 }
573                 delegatedClosures.put(name, Boolean.TRUE);
574                 return makeDelegateToClosureCall(name, desc, signature, exceptions, accessFlags);
575             }
576         } else if ("getProxyTarget".equals(name) && "()Ljava/lang/Object;".equals(desc)) {
577             return createGetProxyTargetMethod(access, name, desc, signature, exceptions);
578         } else if ("<init>".equals(name) && (Modifier.isPublic(access) || Modifier.isProtected(access))) {
579             return createConstructor(access, name, desc, signature, exceptions);
580         } else if (Modifier.isAbstract(access) && !GROOVYOBJECT_METHOD_NAMESS.contains(name)) {
581             if (isImplemented(superClass, name, desc)) {
582                 return null;
583             }
584             accessFlags -= ACC_ABSTRACT;
585             MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
586             mv.visitCode();
587             Type[] args = Type.getArgumentTypes(desc);
588             if (emptyBody) {
589                 Type returnType = Type.getReturnType(desc);
590                 if (returnType==Type.VOID_TYPE) {
591                     mv.visitInsn(RETURN);
592                 } else {
593                     int loadIns = getLoadInsn(returnType);
594                     switch (loadIns) {
595                         case ILOAD: mv.visitInsn(ICONST_0);
596                             break;
597                         case LLOAD: mv.visitInsn(LCONST_0);
598                             break;
599                         case FLOAD: mv.visitInsn(FCONST_0);
600                             break;
601                         case DLOAD: mv.visitInsn(DCONST_0);
602                             break;
603                         default:
604                             mv.visitInsn(ACONST_NULL);
605                     }
606                     mv.visitInsn(getReturnInsn(returnType));
607                     mv.visitMaxs(2, registerLen(args)+1);
608                 }
609             } else {
610                 // for compatibility with the legacy proxy generator, we should throw an UnsupportedOperationException
611                 // instead of an AbtractMethodException
612                 mv.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
613                 mv.visitInsn(DUP);
614                 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "()V", false);
615                 mv.visitInsn(ATHROW);
616                 mv.visitMaxs(2, registerLen(args)+1);
617             }
618             mv.visitEnd();
619         }
620         return null;
621     }
622 
623     private MethodVisitor createGetProxyTargetMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
624         MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_FINAL, name, desc, signature, exceptions);
625         mv.visitCode();
626         mv.visitIntInsn(ALOAD,0);
627         mv.visitFieldInsn(GETFIELD, proxyName, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass));
628         mv.visitInsn(ARETURN);
629         mv.visitMaxs(1,1);
630         mv.visitEnd();
631         return null;
632     }
633 
634     private int registerLen(Type[] args) {
635         int i = 0;
636         for (Type arg : args) {
637             i += registerLen(arg);
638         }
639         return i;
640     }
641 
642     private int registerLen(final Type arg) {
643         return arg== Type.DOUBLE_TYPE||arg==Type.LONG_TYPE?2:1;
644     }
645 
646     private MethodVisitor createConstructor(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
647         Type[] args = Type.getArgumentTypes(desc);
648         StringBuilder newDesc = new StringBuilder("(");
649         for (Type arg : args) {
650             newDesc.append(arg.getDescriptor());
651         }
652         newDesc.append("Ljava/util/Map;"); // the closure map
653         if (generateDelegateField) {
654             newDesc.append(BytecodeHelper.getTypeDescription(delegateClass));
655         }
656         newDesc.append(")V");
657         MethodVisitor mv = super.visitMethod(access, name, newDesc.toString(), signature, exceptions);
658         mv.visitCode();
659         initializeDelegateClosure(mv, args);
660         if (generateDelegateField) {
661             initializeDelegateObject(mv, args);
662         }
663         mv.visitVarInsn(ALOAD, 0);
664         int idx = 1;
665         for (Type arg : args) {
666             if (isPrimitive(arg)) {
667                 mv.visitIntInsn(getLoadInsn(arg), idx);
668             } else {
669                 mv.visitVarInsn(ALOAD, idx); // load argument i
670             }
671             idx += registerLen(arg);
672         }
673         mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(superClass), "<init>", desc, false);
674         mv.visitInsn(RETURN);
675         int max = idx + 1 + (generateDelegateField?1:0);
676         mv.visitMaxs(max, max);
677         mv.visitEnd();
678         return null;
679     }
680 
681     private void initializeDelegateClosure(final MethodVisitor mv, Type[] args) {
682         int idx = 1 + getTypeArgsRegisterLength(args);
683 
684         mv.visitIntInsn(ALOAD, 0); // this
685         mv.visitIntInsn(ALOAD, idx); // constructor arg n is the closure map
686 
687         mv.visitFieldInsn(PUTFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;");
688     }
689 
690     private void initializeDelegateObject(final MethodVisitor mv, Type[] args) {
691         int idx = 2 + getTypeArgsRegisterLength(args);
692 
693         mv.visitIntInsn(ALOAD, 0); // this
694         mv.visitIntInsn(ALOAD, idx); // constructor arg n is the closure map
695         mv.visitFieldInsn(PUTFIELD, proxyName, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass));
696     }
697 
698     private int getTypeArgsRegisterLength(Type[] args)  {
699         int length = 0;
700         for (Type type : args)  { length += registerLen(type); }
701         return length;
702     }
703 
704     /**
705      * Generate a call to the delegate object.
706      */
707     protected MethodVisitor makeDelegateCall(final String name, final String desc, final String signature, final String[] exceptions, final int accessFlags) {
708         MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
709         mv.visitVarInsn(ALOAD, 0); // load this
710         mv.visitFieldInsn(GETFIELD, proxyName, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass)); // load delegate
711         // using InvokerHelper to allow potential intercepted calls
712         int size;
713         mv.visitLdcInsn(name); // method name
714         Type[] args = Type.getArgumentTypes(desc);
715         BytecodeHelper.pushConstant(mv, args.length);
716         mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
717         size = 6;
718         int idx = 1;
719         for (int i = 0; i < args.length; i++) {
720             Type arg = args[i];
721             mv.visitInsn(DUP);
722             BytecodeHelper.pushConstant(mv, i);
723             // primitive types must be boxed
724             if (isPrimitive(arg)) {
725                 mv.visitIntInsn(getLoadInsn(arg), idx);
726                 String wrappedType = getWrappedClassDescriptor(arg);
727                 mv.visitMethodInsn(INVOKESTATIC, wrappedType, "valueOf", "(" + arg.getDescriptor() + ")L" + wrappedType + ";", false);
728             } else {
729                 mv.visitVarInsn(ALOAD, idx); // load argument i
730             }
731             size = Math.max(size, 5+registerLen(arg));
732             idx += registerLen(arg);
733             mv.visitInsn(AASTORE); // store value into array
734         }
735         mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", false);
736         unwrapResult(mv, desc);
737         mv.visitMaxs(size, registerLen(args) + 1);
738 
739         return mv;
740     }
741 
742     protected MethodVisitor makeDelegateToClosureCall(final String name, final String desc, final String signature, final String[] exceptions, final int accessFlags) {
743         MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
744 //        TraceMethodVisitor tmv = new TraceMethodVisitor(mv);
745 //        mv = tmv;
746         mv.visitCode();
747         int stackSize = 0;
748         // method body should be:
749         //  this.$delegate$closure$methodName.call(new Object[] { method arguments })
750         Type[] args = Type.getArgumentTypes(desc);
751         int arrayStore = args.length+1;
752         BytecodeHelper.pushConstant(mv, args.length);
753         mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); // stack size = 1
754         stackSize = 1;
755         int idx = 1;
756         for (int i = 0; i < args.length; i++) {
757             Type arg = args[i];
758             mv.visitInsn(DUP); // stack size = 2
759             BytecodeHelper.pushConstant(mv, i); // array index, stack size = 3
760             stackSize = 3;
761             // primitive types must be boxed
762             if (isPrimitive(arg)) {
763                 mv.visitIntInsn(getLoadInsn(arg), idx);
764                 String wrappedType = getWrappedClassDescriptor(arg);
765                 mv.visitMethodInsn(INVOKESTATIC, wrappedType, "valueOf", "(" + arg.getDescriptor() + ")L" + wrappedType + ";", false);
766             } else {
767                 mv.visitVarInsn(ALOAD, idx); // load argument i
768             }
769             idx += registerLen(arg);
770             stackSize = Math.max(4, 3+registerLen(arg));
771             mv.visitInsn(AASTORE); // store value into array
772         }
773         mv.visitVarInsn(ASTORE, arrayStore); // store array
774         int arrayIndex = arrayStore;
775         mv.visitVarInsn(ALOAD, 0); // load this
776         mv.visitFieldInsn(GETFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;"); // load closure map
777         mv.visitLdcInsn(name); // load method name
778         mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
779         arrayStore++;
780         mv.visitVarInsn(ASTORE, arrayStore);
781         // if null, test if wildcard exists
782         Label notNull = new Label();
783         mv.visitIntInsn(ALOAD, arrayStore);
784         mv.visitJumpInsn(IFNONNULL, notNull);
785         mv.visitVarInsn(ALOAD, 0); // load this
786         mv.visitFieldInsn(GETFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;"); // load closure map
787         mv.visitLdcInsn("*"); // load wildcard
788         mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
789         mv.visitVarInsn(ASTORE, arrayStore);
790         mv.visitLabel(notNull);
791         mv.visitVarInsn(ALOAD, arrayStore);
792         mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(this.getClass()), "ensureClosure", "(Ljava/lang/Object;)Lgroovy/lang/Closure;", false);
793         mv.visitVarInsn(ALOAD, arrayIndex); // load argument array
794         stackSize++;
795         mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Closure", "call", "([Ljava/lang/Object;)Ljava/lang/Object;", false); // call closure
796         unwrapResult(mv, desc);
797         mv.visitMaxs(stackSize, arrayStore+1);
798         mv.visitEnd();
799 //        System.out.println("tmv.getText() = " + tmv.getText());
800         return null;
801     }
802 
803     private void unwrapResult(final MethodVisitor mv, final String desc) {
804         Type returnType = Type.getReturnType(desc);
805         if (returnType==Type.VOID_TYPE) {
806             mv.visitInsn(POP);
807             mv.visitInsn(RETURN);
808         } else {
809             if (isPrimitive(returnType)) {
810                 BytecodeHelper.unbox(mv, ClassHelper.make(returnType.getClassName()));
811             } else {
812                 mv.visitTypeInsn(CHECKCAST, returnType.getInternalName());
813             }
814             mv.visitInsn(getReturnInsn(returnType));
815         }
816     }
817 
818     @SuppressWarnings("unchecked")
819     public GroovyObject proxy(Map<Object,Object> map, Object... constructorArgs) {
820         if (constructorArgs==null && cachedNoArgConstructor!=null) {
821             // if there isn't any argument, we can make invocation faster using the cached constructor
822             try {
823                 return (GroovyObject) cachedNoArgConstructor.newInstance(map);
824             } catch (InstantiationException e) {
825                 throw new GroovyRuntimeException(e);
826             } catch (IllegalAccessException e) {
827                 throw new GroovyRuntimeException(e);
828             } catch (InvocationTargetException e) {
829                 throw new GroovyRuntimeException(e);
830             }
831         }
832         if (constructorArgs==null) constructorArgs= EMPTY_ARGS;
833         Object[] values = new Object[constructorArgs.length + 1];
834         System.arraycopy(constructorArgs, 0, values, 0, constructorArgs.length);
835         values[values.length-1] = map;
836         return DefaultGroovyMethods.<GroovyObject>newInstance(cachedClass, values);
837     }
838 
839     @SuppressWarnings("unchecked")
840     public GroovyObject delegatingProxy(Object delegate,Map<Object,Object> map, Object... constructorArgs) {
841         if (constructorArgs==null && cachedNoArgConstructor!=null) {
842             // if there isn't any argument, we can make invocation faster using the cached constructor
843             try {
844                 return (GroovyObject) cachedNoArgConstructor.newInstance(map, delegate);
845             } catch (InstantiationException e) {
846                 throw new GroovyRuntimeException(e);
847             } catch (IllegalAccessException e) {
848                 throw new GroovyRuntimeException(e);
849             } catch (InvocationTargetException e) {
850                 throw new GroovyRuntimeException(e);
851             }
852         }
853         if (constructorArgs==null) constructorArgs= EMPTY_ARGS;
854         Object[] values = new Object[constructorArgs.length + 2];
855         System.arraycopy(constructorArgs, 0, values, 0, constructorArgs.length);
856         values[values.length-2] = map;
857         values[values.length-1] = delegate;
858         return DefaultGroovyMethods.<GroovyObject>newInstance(cachedClass, values);
859     }
860 
861     /**
862      * Ensures that the provided object is wrapped into a closure if it's not
863      * a closure.
864      * Do not trust IDEs, this method is used in bytecode.
865      */
866     @SuppressWarnings("unchecked")
867     public static Closure ensureClosure(Object o) {
868         if (o==null) throw new UnsupportedOperationException();
869         if (o instanceof Closure) return (Closure) o;
870         return new ReturnValueWrappingClosure(o);
871     }
872 
873     private static int getLoadInsn(final Type type) {
874         if (type == Type.BOOLEAN_TYPE) return ILOAD;
875         if (type == Type.BYTE_TYPE) return ILOAD;
876         if (type == Type.CHAR_TYPE) return ILOAD;
877         if (type == Type.DOUBLE_TYPE) return DLOAD;
878         if (type == Type.FLOAT_TYPE) return FLOAD;
879         if (type == Type.INT_TYPE) return ILOAD;
880         if (type == Type.LONG_TYPE) return LLOAD;
881         if (type == Type.SHORT_TYPE) return ILOAD;
882         return ALOAD;
883     }
884 
885     private static int getReturnInsn(final Type type) {
886         if (type == Type.BOOLEAN_TYPE) return IRETURN;
887         if (type == Type.BYTE_TYPE) return IRETURN;
888         if (type == Type.CHAR_TYPE) return IRETURN;
889         if (type == Type.DOUBLE_TYPE) return DRETURN;
890         if (type == Type.FLOAT_TYPE) return FRETURN;
891         if (type == Type.INT_TYPE) return IRETURN;
892         if (type == Type.LONG_TYPE) return LRETURN;
893         if (type == Type.SHORT_TYPE) return IRETURN;
894         return ARETURN;
895     }
896 
897     private boolean isPrimitive(final Type arg) {
898         return arg == Type.BOOLEAN_TYPE
899                 || arg == Type.BYTE_TYPE
900                 || arg == Type.CHAR_TYPE
901                 || arg == Type.DOUBLE_TYPE
902                 || arg == Type.FLOAT_TYPE
903                 || arg == Type.INT_TYPE
904                 || arg == Type.LONG_TYPE
905                 || arg == Type.SHORT_TYPE;
906     }
907 
908     private String getWrappedClassDescriptor(Type type) {
909         if (type == Type.BOOLEAN_TYPE) return "java/lang/Boolean";
910         if (type == Type.BYTE_TYPE) return "java/lang/Byte";
911         if (type == Type.CHAR_TYPE) return "java/lang/Character";
912         if (type == Type.DOUBLE_TYPE) return "java/lang/Double";
913         if (type == Type.FLOAT_TYPE) return "java/lang/Float";
914         if (type == Type.INT_TYPE) return "java/lang/Integer";
915         if (type == Type.LONG_TYPE) return "java/lang/Long";
916         if (type == Type.SHORT_TYPE) return "java/lang/Short";
917         throw new IllegalArgumentException("Unexpected type class [" + type + "]");
918     }
919 
920     private static class InnerLoader extends GroovyClassLoader {
921         protected InnerLoader(final ClassLoader parent) {
922             super(parent);
923         }
924 
925         public Class defineClass(String name, byte[] data) {
926             return super.defineClass(name, data, 0, data.length);
927         }
928 
929     }
930 
931     private static class ReturnValueWrappingClosure<V> extends Closure<V>{
932         private final V value;
933 
934         public ReturnValueWrappingClosure(V returnValue) {
935             super(null);
936             value = returnValue;
937         }
938 
939         @Override
940         public V call(final Object... args) {
941             return value;
942         }
943     }
944 
945 }